﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;

namespace BuildHelper
{
    public class TelnetConnection : IDisposable
    {
        TcpClient tcpSocket;

        int TimeOutMs = 300;

        public TelnetConnection(string Hostname, int Port)
        {
            tcpSocket = new TcpClient(Hostname, Port);
        }

        public string Login(string Username, string Password, int LoginTimeOutMs)
        {
            int oldTimeOutMs = TimeOutMs;
            TimeOutMs = LoginTimeOutMs;
            string s = Read();
            if (!s.TrimEnd().EndsWith(":"))
                throw new Exception("Failed to connect : no login prompt");
            WriteLine(Username);

            s += Read();
            if (!s.TrimEnd().EndsWith(":"))
                throw new Exception("Failed to connect : no password prompt");
            WriteLine(Password);

            s += Read();
            TimeOutMs = oldTimeOutMs;
            return s;
        }
        public string Login(string Username, int LoginTimeOutMs)
        {
            int oldTimeOutMs = TimeOutMs;
            TimeOutMs = LoginTimeOutMs;
            string s = ReadToEnd();
            bool loginIprompt = false;
            for (int i = 0; i < 7; i++)
            {
                if (!s.XMatch("login:\\s*", System.Text.RegularExpressions.RegexOptions.IgnoreCase | System.Text.RegularExpressions.RegexOptions.Singleline))
                {
                    WriteLine("");
                    System.Threading.Thread.Sleep(20 * i / 2);
                    s = Read();
                }
                else
                {
                    loginIprompt = true;
                    break;
                }
            }
            if (!loginIprompt)
                throw new Exception("Failed to connect: No login prompt.");
            
            WriteLine(Username);

            s += Read();
            TimeOutMs = oldTimeOutMs;
            return s;
        }
        
        public void WriteLine(string cmd)
        {
            Write(cmd + "\n");
        }

        public void Write(string cmd)
        {
            if (!IsConnected) return;
            byte[] buf = System.Text.ASCIIEncoding.ASCII.GetBytes(cmd.Replace("\0xFF", "\0xFF\0xFF"));
            tcpSocket.GetStream().Write(buf, 0, buf.Length);
            for (int i = 0; i < 10; i++)
            {
                System.Threading.Thread.Sleep(25);
                System.Windows.Forms.Application.DoEvents();
            }
        }

        public string Read()
        {
            if (!tcpSocket.Connected) return null;
            StringBuilder sb = new StringBuilder();
            do
            {
                System.Threading.Thread.Sleep(TimeOutMs);
                ParseTelnet(sb);
            } while (tcpSocket.Available > 0);
            return sb.ToString();
        }
        public string ReadToEnd()
        {
            StringBuilder sb = new StringBuilder();
            bool finish = false;
            while (true)
            {
                try
                {
                    string s = Read();

                    if (s.IsNullOrEmpty())
                    {
                        if (finish)
                            break;
                        else
                        {
                            finish = true;
                            System.Threading.Thread.Sleep(TimeOutMs);
                        }
                    }
                    else
                    {
                        sb.Append(s);
                        System.Threading.Thread.Sleep(150);
                    }
                }
                catch
                {
                    break;
                }
            }
            return sb.ToString();
        }

        public bool IsConnected
        {
            get { return Kontakt.KontaktTools.SocketConnected( tcpSocket.Client); }
        }

        void ParseTelnet(StringBuilder sb)
        {
            while (tcpSocket.Available > 0)
            {
                int input = tcpSocket.GetStream().ReadByte();
                switch (input)
                {
                    case -1:
                        break;
                    case (int)Verbs.IAC:
                        // interpret as command
                        int inputverb = tcpSocket.GetStream().ReadByte();
                        if (inputverb == -1) break;
                        switch (inputverb)
                        {
                            case (int)Verbs.IAC:
                                //literal IAC = 255 escaped, so append char 255 to string
                                sb.Append(inputverb);
                                break;
                            case (int)Verbs.DO:
                            case (int)Verbs.DONT:
                            case (int)Verbs.WILL:
                            case (int)Verbs.WONT:
                                // reply to all commands with "WONT", unless it is SGA (suppres go ahead)
                                int inputoption = tcpSocket.GetStream().ReadByte();
                                if (inputoption == -1) break;
                                tcpSocket.GetStream().WriteByte((byte)Verbs.IAC);
                                if (inputoption == (int)Options.SGA)
                                    tcpSocket.GetStream().WriteByte(inputverb == (int)Verbs.DO ? (byte)Verbs.WILL : (byte)Verbs.DO);
                                else
                                    tcpSocket.GetStream().WriteByte(inputverb == (int)Verbs.DO ? (byte)Verbs.WONT : (byte)Verbs.DONT);
                                tcpSocket.GetStream().WriteByte((byte)inputoption);
                                break;
                            default:
                                break;
                        }
                        break;
                    default:
                        sb.Append((char)input);
                        break;
                }
            }
        }

        enum Verbs
        {
            WILL = 251,
            WONT = 252,
            DO = 253,
            DONT = 254,
            IAC = 255
        }

        enum Options
        {
            SGA = 3
        }

        public void Dispose()
        {
            try
            {
                if (Kontakt.KontaktTools.SocketConnected(tcpSocket.Client))
                {
                    tcpSocket.Client.Disconnect(false);
                    tcpSocket.Client.Close();
                }
            }
            catch { }
            try
            {
                if (tcpSocket.Connected)
                    tcpSocket.Close();
                tcpSocket = null;
            }
            catch { }
        }
    }
}
